package naga.x.util;

import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import gnu.trove.TLongCollection;
import gnu.trove.iterator.TLongIterator;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.procedure.TLongProcedure;
import gnu.trove.queue.TLongQueue;

/**
 * 有界阻塞long队列
 * @author yang.li
 *
 */
public class TLongArrayBlockingQueue implements TLongQueue {
	
	private final long[] values;
	private final long noEntryValue;
	private int takeIndex, putIndex;
	private int count;
	
	private final Lock lock;
	
	public TLongArrayBlockingQueue() {
		this(10);
	}
	
	public TLongArrayBlockingQueue(int capacity) {
		this(capacity, 0);
	}
	
	public TLongArrayBlockingQueue(int capacity, long noEntryValue) {
		this.values = new long[capacity];
		this.noEntryValue = noEntryValue;
		this.lock = new ReentrantLock();
	}

	@Override
	public long getNoEntryValue() {
		return noEntryValue;
	}

	@Override
	public int size() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			return count;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean isEmpty() {
		return size() == 0;
	}

	@Override
	public boolean contains(long entry) {
		if (entry == noEntryValue) {
			return false;
		}
		final Lock lock = this.lock;
		lock.lock();
		try {
			if (count > 0) {
				final long[] vals = values;
				final int n = putIndex;
				int i = takeIndex;
				do {
					if (entry == vals[i]) {
						return true;
					}
					if (++i == vals.length) {
						i = 0;
					}
				} while (i != n);
			}
			return false;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public TLongIterator iterator() {
		return new TLongQueueIterator();
	}

	@Override
	public long[] toArray() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			if (count > 0) {
				final long[] vals = values;
				long[] ret = new long[count];
				int from = takeIndex;
				int to = putIndex;
				if (to > from) {
					System.arraycopy(vals, from, ret, 0, to - from);
				} else {
					// behind in the array
					int len = vals.length - from;
					System.arraycopy(vals, from, ret, 0, len);
					// front in the array
					System.arraycopy(vals, 0, ret, len, to);
				}
				return ret;
			}
			return new long[0];
		} finally {
			lock.unlock();
		}
	}

	@Override
	public long[] toArray(long[] dest) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			if (count > 0) {
				long[] vals = values;
				int offset = takeIndex;
				int end = putIndex;
				int len = dest.length;
				for (int i = 0; i < len; i++) {
					dest[i] = vals[offset];
					if (++offset == vals.length) {
						offset = 0;
					}
					if (offset == end) {
						break;
					}
				}
			}
			return dest;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean add(long entry) {
		return offer(entry);
	}

	@Override
	public boolean remove(long entry) {
		if (entry == noEntryValue) {
			return false;
		}
		final Lock lock = this.lock;
		lock.lock();
		try {
			if (count > 0) {
				final long[] vals = values;
				final int n = putIndex;
				int i = takeIndex;
				do {
					if (entry == vals[i]) {
						removeAt(i);
						return true;
					}
					if (++i == vals.length) {
						i = 0;
					}
				} while (i != n);
			}
			return false;
		} finally {
			lock.unlock();
		}
	}
	
	private void removeAt(final int index) {
		final long[] vals = values;
		if (index == takeIndex) {
			// no need foreach when the index is the first value
			vals[takeIndex] = noEntryValue;
			if (++takeIndex == vals.length) {
				takeIndex = 0;
			}
		} else {
			final int end = putIndex;
			for (int i = index;;) {
				int next = i + 1;
				if (next == vals.length) {
					next = 0;
				}
				if (next == end) {
					vals[i] = noEntryValue;
					putIndex = i;
					break;
				}
				vals[i] = vals[next];
				i = next;
			}
		}
		count--;
	}

	@Override
	public boolean containsAll(Collection<?> collection) {
		for (Object o : collection) {
			if (!(o instanceof Long)) {
				return false;
			}
			if (!contains(((Long) o).longValue())) {
				return false;
			}
		}
		return true;
	}

	@Override
	public boolean containsAll(TLongCollection collection) {
		return containsAll(collection.toArray());
	}

	@Override
	public boolean containsAll(long[] array) {
		for (long v : array) {
			if (!contains(v)) {
				return false;
			}
		}
		return true;
	}

	@Override
	public boolean addAll(Collection<? extends Long> collection) {
		for (Long v : collection) {
			if (!offer(v.longValue())) {
				return false;
			}
		}
		return true;
	}

	@Override
	public boolean addAll(TLongCollection collection) {
		return addAll(collection.toArray());
	}

	@Override
	public boolean addAll(long[] array) {
		for (long v : array) {
			if (!offer(v)) {
				return false;
			}
		}
		return true;
	}

	@Override
	public boolean retainAll(Collection<?> collection) {
		boolean changed = false;
		TLongIterator itr = iterator();
		while (itr.hasNext()) {
			long v = itr.next();
			if (!collection.contains(v)) {
				itr.remove();
				changed = true;
			}
		}
		return changed;
	}

	@Override
	public boolean retainAll(TLongCollection collection) {
		boolean changed = false;
		TLongIterator itr = iterator();
		while (itr.hasNext()) {
			long v = itr.next();
			if (!collection.contains(v)) {
				itr.remove();
				changed = true;
			}
		}
		return changed;
	}

	@Override
	public boolean retainAll(long[] array) {
		return retainAll(new TLongArrayList(array));
	}

	@Override
	public boolean removeAll(Collection<?> collection) {
		boolean changed = false;
		TLongIterator itr = iterator();
		while (itr.hasNext()) {
			long v = itr.next();
			if (collection.contains(v)) {
				itr.remove();
				changed = true;
			}
		}
		return changed;
	}

	@Override
	public boolean removeAll(TLongCollection collection) {
		boolean changed = false;
		TLongIterator itr = iterator();
		while (itr.hasNext()) {
			long v = itr.next();
			if (collection.contains(v)) {
				itr.remove();
				changed = true;
			}
		}
		return changed;
	}

	@Override
	public boolean removeAll(long[] array) {
		return removeAll(new TLongArrayList(array));
	}

	@Override
	public void clear() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			count = 0;
			takeIndex = putIndex = 0;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean forEach(TLongProcedure procedure) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			if (count == 0) {
				return false;
			}
			final long[] vals = values;
			final int end = putIndex;
			for (int i = takeIndex;;) {
				long v = vals[i];
				if (!procedure.execute(v)) {
					return false;
				}
				if (++i == vals.length) {
					i = 0;
				}
				if (i == end) {
					break;
				}
			}
			return true;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public long element() {
		long v = poll();
		if (v == noEntryValue) {
			throw new NullPointerException();
		}
		return v;
	}

	@Override
	public boolean offer(long e) {
		if (e == noEntryValue) {
			return false;
		}
		final Lock lock = this.lock;
		lock.lock();
		try {
			final long[] vals = values;
			if (count == vals.length) { // queue full
				return false;
			}
			vals[putIndex] = e;
			if (++putIndex == vals.length) {
				putIndex = 0;
			}
			count++;
		} finally {
			lock.unlock();
		}
		return false;
	}

	@Override
	public long peek() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			return count > 0 ? values[takeIndex] : noEntryValue;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public long poll() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			if (count > 0) {
				final long[] vals = values;
				long v = vals[takeIndex];
				vals[takeIndex] = noEntryValue;
				if (++takeIndex == vals.length) {
					takeIndex = 0;
				}
				count--;
				return v;
			}
			return noEntryValue;
		} finally {
			lock.unlock();
		}
	}
	
	@Override
	public String toString() {
		return Arrays.toString(toArray());
	}

	class TLongQueueIterator implements TLongIterator {
		
		int lastIndex, nextIndex;
		
		TLongQueueIterator() {
			Lock lock = TLongArrayBlockingQueue.this.lock;
			lock.lock();
			try {
				if (TLongArrayBlockingQueue.this.count > 0) {
					lastIndex = nextIndex = TLongArrayBlockingQueue.this.takeIndex;
				} else {
					lastIndex = nextIndex = -1;
				}
			} finally {
				lock.unlock();
			}
		}
		
		@Override
		public boolean hasNext() {
			return nextIndex != -1;
		}

		@Override
		public void remove() {
			Lock lock = TLongArrayBlockingQueue.this.lock;
			lock.lock();
			try {
				if (lastIndex == -1) {
					return;
				}
				if (lastIndex != TLongArrayBlockingQueue.this.takeIndex) {
					// the array component position moved @see #removeAt
					nextIndex = lastIndex;
				}
				TLongArrayBlockingQueue.this.removeAt(lastIndex);
				lastIndex = -1;
			} finally {
				lock.unlock();
			}
		}

		@Override
		public long next() {
			Lock lock = TLongArrayBlockingQueue.this.lock;
			lock.lock();
			try {
				lastIndex = nextIndex;
				if (nextIndex >= 0) {
					long[] vals = TLongArrayBlockingQueue.this.values;
					long v = vals[nextIndex];
					// move to next
					if (++nextIndex == vals.length) {
						nextIndex = 0;
					}
					if (nextIndex == TLongArrayBlockingQueue.this.putIndex) {
						nextIndex = -1;
					}
					return v;
				}
			} finally {
				lock.unlock();
			}
			return TLongArrayBlockingQueue.this.noEntryValue;
		}
		
	}

}
